<?php
/**
 * Created by 784855684@qq.com.
 * Link: https://github.com/lichtung/wshore
 * User: linzh
 * Date: 7/8/17
 * Time: 12:21 PM
 */
declare(strict_types=1);


namespace application\module\admin\addon\wechat;


use application\module\admin\addon\wechat\model\PaymentModel;
use wshore\core\Logger;
use wshore\helper\Chars;
use wshore\helper\Network;
use wshore\throws\network\PageNotFountException;
use wshore\throws\NetworkException;

class Enterprise
{

    const NO_CHECK = 'NO_CHECK';
    const FORCE_CHECK = 'FORCE_CHECK';
    const OPTION_CHECK = 'OPTION_CHECK';

    protected $params = [
        'mch_appid' => '',
        'mchid' => '',
        'nonce_str' => '',
        'partner_trade_no' => '',
        'openid' => '',
        'check_name' => '',
        're_user_name' => '',
        'amount' => '',//单位:0.01元
        'desc' => '',
        'spbill_create_ip' => '',
    ];

    private $pay_api_secret = '';

    public function __construct(string $mch_appid, string $api_secret, string $mchid, string $spbill_create_ip)
    {
        //企业方
        $this->params['mch_appid'] = $mch_appid;
        $this->params['mchid'] = $mchid;
        $this->params['spbill_create_ip'] = $spbill_create_ip;
        $this->pay_api_secret = $api_secret;
    }

    /**
     * 向个人付款
     * $wechat = new Enterprise();
     * $info = $wechat->pay('微信openid', 100, 'hello world', $check = Enterprise::FORCE_CHECK, $realname = '实名认证姓名');
     *
     *
     * 成功或者失败返回如下:
     *
     * 失败：
     *          array (
     *              'return_code' => 'SUCCESS',
     *              'return_msg' => '真实姓名不一致.',
     *              'result_code' => 'FAIL',
     *              'err_code' => 'NAME_MISMATCH',
     *              'err_code_des' => '真实姓名不一致.',
     *          )
     * 成功：
     *          array (
     *              'return_code' => 'SUCCESS',
     *              'return_msg' => SimpleXMLElement::__set_state(array()),
     *              'mch_appid' => 'wx29530c2c3cf68e00',
     *              'mchid' => '1289090001',
     *              'device_info' =>SimpleXMLElement::__set_state(array()),
     *              'nonce_str' => 'tIxdMRtpIwMdgtQg8QgAkQhk',
     *              'result_code' => 'SUCCESS',
     *              'partner_trade_no' => 'd8f4b1eead7c1772afb2df84ef8e3c55',
     *              'payment_no' => '1000018301201612285712680039',
     *              'payment_time' => '2016-12-28 15:04:11',
     *          )
     *
     * @param string $openid 收款者微信openid
     * @param int $amount 金额，微信要求每次付款金额必须大于1元
     * @param string $desc 入款详情
     * @param string $check 检查级别，NO_CHECK 不检查实名验证 ，FORCE_CHECK 检查实名验证 ，OPTION_CHECK
     * @param string $realname 检查级别为FORCE_CHECK时需要输入的真实姓名
     * @return bool
     */
    public function pay($openid, $amount, $desc, $check = self::FORCE_CHECK, $realname = ''): bool
    {
        $model = new PaymentModel();
        $model->inTransaction() or $model->beginTransaction();
        //企业方
        $model->mch_appid = $this->params['mch_appid'];
        $model->mchid = $this->params['mchid'];
        $model->spbill_create_ip = $this->params['spbill_create_ip'];
        //目标账户
        $this->params['openid'] = $model->openid = $openid;//借款账户OPENID
        $this->params['amount'] = $model->amount = $amount;;//付款金额，单位为分
        $this->params['desc'] = $model->desc = $desc;;//付款描述
        //实名验证
        $this->params['check_name'] = $model->check_name = $check;
        $this->params['re_user_name'] = $model->re_user_name = $realname;

        //其他
        $this->params['nonce_str'] = $model->nonce_str = Chars::random(24);//随机字符串，不长于32位
        $this->params['partner_trade_no'] = $model->partner_trade_no = md5(microtime(true) . $openid);

        $sign = $this->make_sign($this->params);

        //note:缺少device_info
        $requestData = "<xml>
<mch_appid>{$this->params['mch_appid']}</mch_appid>
<mchid>{$this->params['mchid']}</mchid>
<nonce_str>{$this->params['nonce_str']}</nonce_str>
<partner_trade_no>{$this->params['partner_trade_no']}</partner_trade_no>
<openid>{$this->params['openid']}</openid>
<check_name>{$this->params['check_name']}</check_name>
<re_user_name>{$this->params['re_user_name']}</re_user_name>
<amount>{$this->params['amount']}</amount>
<desc>{$this->params['desc']}</desc>
<spbill_create_ip>{$this->params['spbill_create_ip']}</spbill_create_ip>
<sign>{$sign}</sign>
</xml>";

        try {
            $content = self::curl_post_ssl('https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers', $requestData);
            $msg = (array)simplexml_load_string($content, \SimpleXMLElement::class, LIBXML_NOCDATA);

            Logger::getLogger('addon.wechat.pay')->info([
                'content' => $content,
                'params' => $this->params,
            ]);

            $return_msg = $msg['return_msg']??'';
            $model->error_code = $msg['return_code']??'';
            $model->error_msg = is_string($return_msg) ? $return_msg : '';

            if (empty($msg['return_code']) or 'SUCCESS' !== $msg['return_code'] or empty($msg['result_code'])) {
                Logger::getLogger('addon.wechat.pay')->fatal([
                    'type' => 'badres', 'reqdata' => $requestData, 'result' => $content
                ]);
                return false;
            } else {
                if ($res = $msg['result_code'] === 'SUCCESS') {
                    $model->payment_no = $msg['payment_no']??'';
                    $model->payment_time = $msg['payment_time'];
                    $model->result = 'S';
                } else {
                    $model->result = 'F';
                }
                $model->save();
                return $res;
            }
        } catch (NetworkException $exception) {
            $message = var_export([
                'network', $exception->getMessage(), $this->params,
            ], true);
            Logger::getLogger('addon.wechat.pay')->fatal($message);
            $requesturl = WS_HOST_URL . '/api/sendemail?' . http_build_query([
                    'subject' => '微信支付保存订单记录出错',
                    'body' => $message,
                    'secret' => 'ef6efbcf9e48bf1be79a6b323321204d',
                ]);
            Network::getJson($requesturl);
            return false;
        }
    }

    protected function make_sign($paraMap = array())
    {
        $buff = "";
        ksort($paraMap);
        //如果参数的值为空不参与签名
        foreach ($paraMap as $k => $v) {
            if (null != $v && 'null' != $v && '' != $v && 'sign' != $k) {
                $buff .= strtolower($k) . '=' . $v . '&';
            }
        }
        $buff .= 'key=' . $this->pay_api_secret;
        return strtoupper(md5($buff));
    }

    private static function curl_post_ssl($url, $vars, $second = 30, $aHeader = array())
    {
        $ch = curl_init();
        //超时时间
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        //这里设置代理，如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '10.206.30.98');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

        //以下两种方式需选择一种

        //第一种方法，cert 与 key 分别属于两个.pem文件
        //默认格式为PEM，可以注释
        curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
        curl_setopt($ch, CURLOPT_SSLCERT, WS_PATH_DATA . '/cert/apiclient_cert.pem');
        //默认格式为PEM，可以注释
        curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
        curl_setopt($ch, CURLOPT_SSLKEY, WS_PATH_DATA . '/cert/apiclient_key.pem');

        //第二种方式，两个文件合成一个.pem文件
//        curl_setopt($ch,CURLOPT_SSLCERT,getcwd().'/all.pem');

        $aHeader and curl_setopt($ch, CURLOPT_HTTPHEADER, $aHeader);

        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $vars);


        $content = curl_exec($ch);
        $httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($httpCode === 404) {
            throw new PageNotFountException($url);
        }
        if (false === $content) {
            throw new NetworkException($url);
        }
        return (string)$content;
    }
}